﻿// -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	SmartLib
//  File name:	MapReduce.cs
//  Developer:	Igor Solomatov
//  Created:	04.11.2012
// 
// -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.Linq;
using Smart.Iterators;

namespace Smart
{
	public static class MapReduceExts
	{
		/// <summary>
		/// Returns new enumerable of changed items by <paramref name="mapper"/>
		/// </summary>
		public static IEnumerable<TResultValue> Map<TResultValue, TSourceValue>(this IEnumerable<TSourceValue> items, Func<TSourceValue, TResultValue> mapper)
		{
			return
				items != null
					? items.Select(mapper)
					: null;
		}

		/// <summary>
		/// Changes all items by <paramref name="mapper"/>
		/// </summary>
		public static void Remap<TValue>(this IList<TValue> items, Func<TValue, TValue> mapper)
		{
			if (items == null)
				return;

			for (int i = 0, iMax = items.Count; i < iMax; i++)
				items[i] = mapper(items[i]);
		}

		/// <summary>
		/// Returns new sliding approximator for <paramref name="items"/> with <paramref name="size"/> values window
		/// </summary>
		/// <param name="items">Source items collection</param>
		/// <param name="startIdx">Starting index in <paramref name="items"/> collection</param>
		/// <param name="size">Size of approximation window</param>
		/// <param name="initial">Initial internal accumulator value</param>
		/// <param name="adder">Adds source value to internal accumulator</param>
		/// <param name="remover">Removes source valur from internal accumulator</param>
		/// <param name="divider">Divides (or calculates) approximated value</param>
		public static SlidingApproximator<TResultValue, TInternalValue, TSourceValue> GetSlidingApproximator<TResultValue, TInternalValue, TSourceValue>(this IList<TSourceValue> items, int startIdx, int size, TInternalValue initial, Func<TInternalValue, TSourceValue, TInternalValue> adder, Func<TInternalValue, TSourceValue, TInternalValue> remover, Func<TInternalValue, int, TResultValue> divider)
		{
			if (items == null)
				return null;

			var app = new SlidingApproximator<TResultValue, TInternalValue, TSourceValue>(size, initial, adder, remover, divider);

			return
				app.Init(items, startIdx)
					? app
					: null;
		}

		/// <summary>
		/// Returns new enumerable of sliding approximated <paramref name="items"/> with <paramref name="size"/> values window
		/// </summary>
		/// <param name="items">Source items collection</param>
		/// <param name="size">Size of approximation window</param>
		/// <param name="initial">Initial internal accumulator value</param>
		/// <param name="adder">Adds source value to internal accumulator</param>
		/// <param name="remover">Removes source valur from internal accumulator</param>
		/// <param name="divider">Divides (or calculates) approximated value</param>
		public static IEnumerable<TResultValue> SlidingApproximate<TResultValue, TInternalValue, TSourceValue>(this IEnumerable<TSourceValue> items, int size, TInternalValue initial, Func<TInternalValue, TSourceValue, TInternalValue> adder, Func<TInternalValue, TSourceValue, TInternalValue> remover, Func<TInternalValue, int, TResultValue> divider)
		{
			return
				items != null
					? new SlidingApproximationEnumerableIterator<TResultValue, TInternalValue, TSourceValue>(items, new SlidingApproximator<TResultValue, TInternalValue, TSourceValue>(size, initial, adder, remover, divider))
					: null;
		}

		/// <summary>
		/// Reduce all items to one value by <paramref name="reducer"/> with <paramref name="initial"/> value
		/// </summary>
		public static TResultValue Reduce<TResultValue, TSourceValue>(this IEnumerable<TSourceValue> items, TResultValue initial, Func<TResultValue, TSourceValue, TResultValue> reducer)
		{
			return
				items != null
					? items.Aggregate(initial, reducer)
					: initial;
		}

		/// <summary>
		/// Reduce all items to shorter sequence, each new item in result sequence identified by <paramref name="groupChecker"/>, items in one group accumulated by <paramref name="reducer"/> with <paramref name="initial"/> value
		/// </summary>
		public static IEnumerable<TResultValue> Reduce<TResultValue, TSourceValue>(this IEnumerable<TSourceValue> items, TResultValue initial, Func<TSourceValue, bool> groupChecker, Func<TResultValue, TSourceValue, TResultValue> reducer)
		{
			return
				items != null
					? new GroupedReduceEnumerableIterator<TResultValue, TSourceValue>(items, initial, groupChecker, reducer)
					: null;
		}

		/// <summary>
		/// Reduce all items to shorter dictionary, sequences identified by <paramref name="keySelector"/>, items in one group accumulated by <paramref name="reducer"/> with <paramref name="initial"/> value
		/// </summary>
		public static Dictionary<TKeyValue, TResultValue> MapReduce<TKeyValue, TResultValue, TSourceValue>(this IEnumerable<TSourceValue> items, Func<TSourceValue, TKeyValue> keySelector, TResultValue initial, Func<TResultValue, TSourceValue, TResultValue> reducer)
		{
			return MapReduce(items, keySelector, null, initial, reducer);
		}

		/// <summary>
		/// Reduce all items to shorter dictionary with specified <paramref name="comparer"/>, sequences identified by <paramref name="keySelector"/>, items in one group accumulated by <paramref name="reducer"/> with <paramref name="initial"/> value
		/// </summary>
		public static Dictionary<TKeyValue, TResultValue> MapReduce<TKeyValue, TResultValue, TSourceValue>(this IEnumerable<TSourceValue> items, Func<TSourceValue, TKeyValue> keySelector, IEqualityComparer<TKeyValue> comparer, TResultValue initial, Func<TResultValue, TSourceValue, TResultValue> reducer)
		{
			if (items == null)
				return null;

			var res = new Dictionary<TKeyValue, TResultValue>(comparer);

			foreach (var item in items)
			{
				var key = keySelector(item);

				// ReSharper disable CompareNonConstrainedGenericWithNull
				if (key == null)
					continue;
				// ReSharper restore CompareNonConstrainedGenericWithNull

				TResultValue value;

				if (!res.TryGetValue(key, out value))
					value = initial;

				res[key] = reducer(value, item);
			}

			return res;
		}


		/// <summary>
		/// Reduce all items to shorter dictionary, sequences identified by <paramref name="keyValueSelector"/>, items in one group accumulated by <paramref name="reducer"/> with <paramref name="initial"/> value
		/// </summary>
		public static Dictionary<TKeyValue, TResultValue> MapReduce<TKeyValue, TResultValue, TInternalValue, TSourceValue>(this IEnumerable<TSourceValue> items, Func<TSourceValue, KeyValuePair<TKeyValue, TInternalValue>> keyValueSelector, TResultValue initial, Func<TResultValue, TInternalValue, TResultValue> reducer)
		{
			return MapReduce(items, keyValueSelector, null, initial, reducer);
		}

		/// <summary>
		/// Reduce all items to shorter dictionary with specified <paramref name="comparer"/>, sequences identified by <paramref name="keyValueSelector"/>, items in one group accumulated by <paramref name="reducer"/> with <paramref name="initial"/> value
		/// </summary>
		public static Dictionary<TKeyValue, TResultValue> MapReduce<TKeyValue, TResultValue, TInternalValue, TSourceValue>(this IEnumerable<TSourceValue> items, Func<TSourceValue, KeyValuePair<TKeyValue, TInternalValue>> keyValueSelector, IEqualityComparer<TKeyValue> comparer, TResultValue initial, Func<TResultValue, TInternalValue, TResultValue> reducer)
		{
			if (items == null)
				return null;

			var res = new Dictionary<TKeyValue, TResultValue>(comparer);

			foreach (var item in items)
			{
				var kvp = keyValueSelector(item);

				var key = kvp.Key;
				var ivalue = kvp.Value;

				// ReSharper disable CompareNonConstrainedGenericWithNull
				if (key == null)
					continue;
				// ReSharper restore CompareNonConstrainedGenericWithNull

				TResultValue value;

				if (!res.TryGetValue(key, out value))
					value = initial;

				res[key] = reducer(value, ivalue);
			}

			return res;
		}

		#region Helpers

		private class GroupedReduceEnumerableIterator<TResultValue, TSourceValue> : Iterator<TResultValue>
		{
			private readonly IEnumerable<TSourceValue> _source;
			private readonly TResultValue _initial;
			private readonly Func<TSourceValue, bool> _groupChecker;
			private readonly Func<TResultValue, TSourceValue, TResultValue> _reducer;

			private TSourceValue _firstInGroupValue;
			private IEnumerator<TSourceValue> _enumerator;

			public GroupedReduceEnumerableIterator(IEnumerable<TSourceValue> source, TResultValue initial, Func<TSourceValue, bool> groupChecker, Func<TResultValue, TSourceValue, TResultValue> reducer)
			{
				_source = source;
				_initial = initial;
				_groupChecker = groupChecker;
				_reducer = reducer;
			}

			public override Iterator<TResultValue> Clone()
			{
				return new GroupedReduceEnumerableIterator<TResultValue, TSourceValue>(_source, _initial, _groupChecker, _reducer);
			}

			public override void Dispose()
			{
				if (_enumerator != null)
					_enumerator.Dispose();

				_enumerator = null;

				base.Dispose();
			}

			public override bool MoveNext()
			{
				switch (State)
				{
					case 1:
						_enumerator = _source.GetEnumerator();
						State = 2;

						var accumulated = false;
						var accumulator = _initial;

						while (_enumerator.MoveNext())
						{
							var current = _enumerator.Current;

							if (_groupChecker(current) && accumulated)
							{
								State = 2;
								_firstInGroupValue = current;
								CurrentValue = accumulator;
								return true;
							}

							accumulated = true;
							accumulator = _reducer(accumulator, current);
						}

						State = 3;

						if (accumulated)
						{
							CurrentValue = accumulator;
							return true;
						}
						break;

					case 2:
						accumulator = _reducer(_initial, _firstInGroupValue);

						while (_enumerator.MoveNext())
						{
							var current = _enumerator.Current;

							if (_groupChecker(current))
							{
								_firstInGroupValue = current;
								CurrentValue = accumulator;
								return true;
							}

							accumulator = _reducer(accumulator, current);
						}

						State = 3;

						CurrentValue = accumulator;
						return true;

					case 3:
						Dispose();
						break;
				}

				return false;
			}

			protected override void ResetImpl()
			{
				if (_enumerator != null)
					_enumerator.Dispose();

				_enumerator = null;
			}
		}

		private class SlidingApproximationEnumerableIterator<TResultValue, TInternalValue, TSourceValue> : Iterator<TResultValue>
		{
			private readonly IEnumerable<TSourceValue> _source;
			private readonly SlidingApproximator<TResultValue, TInternalValue, TSourceValue> _slidingApproximator;

			private IEnumerator<TSourceValue> _enumerator;

			public SlidingApproximationEnumerableIterator(IEnumerable<TSourceValue> source, SlidingApproximator<TResultValue, TInternalValue, TSourceValue> slidingApproximator)
			{
				_source = source;
				_slidingApproximator = slidingApproximator;
			}

			public override Iterator<TResultValue> Clone()
			{
				return new SlidingApproximationEnumerableIterator<TResultValue, TInternalValue, TSourceValue>(_source, _slidingApproximator);
			}

			public override void Dispose()
			{
				if (_enumerator != null)
					_enumerator.Dispose();

				_enumerator = null;

				_slidingApproximator.Reset();

				base.Dispose();
			}

			public override bool MoveNext()
			{
				switch (State)
				{
					case 1:
						_enumerator = _source.GetEnumerator();

						State = 2;

						if (_slidingApproximator.Init(_enumerator))
						{
							CurrentValue = _slidingApproximator.Current;
							return true;
						}

						State = 3;
						goto case 3;

					case 2:
						if (_slidingApproximator.Next(_enumerator))
						{
							CurrentValue = _slidingApproximator.Current;
							return true;
						}

						State = 3;
						goto case 3;

					case 3:
						Dispose();
						break;
				}

				return false;
			}

			protected override void ResetImpl()
			{
				if (_enumerator != null)
					_enumerator.Dispose();

				_enumerator = null;

				_slidingApproximator.Reset();
			}
		}

		#endregion
	}
}